/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.cbkst2.prereq.structurevis;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ListModel;
import javax.swing.table.TableModel;

import at.tugraz.kmi.css.cbkst2.CbKSTCompetence;
import at.tugraz.kmi.css.cbkst2.PERSISTENCE_TYPE;
import at.tugraz.kmi.css.cbkst2.PrerequesiteRelation;
import at.tugraz.kmi.css.cbkst2.prereq.CompetenceManager;
import at.tugraz.kmi.css.cbkst2.prereq.PrerequesiteOrderSetGraph;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.TreeList;
import ca.odell.glazedlists.TreeList.ExpansionModel;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventListModel;
import ca.odell.glazedlists.swing.EventTableModel;
import ca.odell.glazedlists.swing.EventTreeModel;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.jgoodies.binding.PresentationModel;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.list.SelectionInList;

/**
 * In the Competence Manager Model the magic synchronization between the GUI and
 * the domain model happens.
 * 
 * See Presentation Model Pattern (Martin Fowler)
 * 
 * @author Georg Öttl <georg.oettl@gmail.com>
 */
@Singleton
public final class CompetenceManagerModel extends
		PresentationModel<CompetenceManager> {

	/**
	 * Public available property that for the enable/disable of the
	 * TRANSITIVE_CLOSURE_VIEW.
	 */
	public static final String							PROPERTY_SHOW_TRANSITIVE_CLOSURE_VIEW	= "showTransitiveClosureView";

	private static final long							serialVersionUID						= 1L;

	private SelectionInList<CbKSTCompetence>			competenceSelection;

	private transient EventTreeModel<CbKSTCompetence>	competencesTreeModel;

	private boolean										showTransitiveClosureView;

	private final CompetenceModel						competenceModel;

	private final CompetenceManager						competenceManager;

	private transient ListModel							competenceListModel;

	private TableModel									competenceTableModel;

	private transient FilterList<CbKSTCompetence>		competencesEventList;

	private transient PrerequesiteTreeListFormat		competencesTreeListFormat;

	private transient final Logger						log;

	@Inject
	CompetenceManagerModel(CompetenceManager competenceManager,
			CompetenceModel competenceModel, Logger logger) {
		super(competenceManager);
		this.log = logger;
		this.competenceManager = competenceManager;
		this.competenceModel = competenceModel;
		initModels();
		initEventHandling();
	}

	/**
	 * This action commits all changes in the model to the domain layer.
	 * 
	 * 
	 * @return The apply changes action.
	 */
	public Action getApplyAction() {
		return new ApplyAction();
	}

	/**
	 * @return A set of available PERSISTENCE TYPES on runtime.
	 * @see CompetenceManager
	 */
	public List<PERSISTENCE_TYPE> getAvailablePersistenceTypes() {
		return this.competenceManager.getLoaderTypes();
	}

	/**
	 * Returns the List of Competences with the current selection. Useful to
	 * display the managed Competences in a JList or JTable.
	 * 
	 * @return the List of Competences with selection
	 */
	public SelectionInList<CbKSTCompetence> getCompetenceSelection() {
		return competenceSelection;
	}

	/**
	 * The eventTreeModel of the underlying list of competences.
	 * 
	 * @return The eventTreeModel of the underlying list of competences.
	 */
	public EventTreeModel<CbKSTCompetence> getCompetencesTreeModel() {
		return competencesTreeModel;
	}

	/**
	 * The Table model of the underlying competences.
	 * 
	 * @return The Table model of the underlying competences.
	 */
	public TableModel getCompetenceTableModel() {
		return competenceTableModel;
	}

	/**
	 * Set in the Presentation Layer if the Transitive Closure View should be
	 * visible
	 * 
	 * @return true if visible/enabled
	 */
	public boolean isShowTransitiveClosureView() {
		return showTransitiveClosureView;
	}

	/**
	 * Loads a competence structure into the domain model.
	 * 
	 * @param f
	 * @param loader
	 * @throws FileNotFoundException
	 *             If file not Found
	 * @see CompetenceManager
	 */
	public void loadStructure(File f, PERSISTENCE_TYPE loader)
			throws FileNotFoundException {
		loadStructure(new FileInputStream(f), loader);
	}

	/**
	 * Loads a competence structure into the domain model.
	 * 
	 * @param in
	 *            The Inputstream to load the structure from
	 * @param loader
	 *            The loaded to use for loading data from the stream.
	 */
	public void loadStructure(InputStream in, PERSISTENCE_TYPE loader) {
		getBean().loadStructure(in, loader);
	}

	/**
	 * Loads a competence structure into the domain model.
	 * 
	 * @param f
	 *            The file to load the structure from
	 * @throws FileFormatError
	 *             If there is an error with the extension
	 * @throws FileNotFoundException
	 *             If file not Found
	 */
	public void loadStructureFromExtension(File f) throws FileFormatError,
			FileNotFoundException {
		getBean().loadStructureFromExtension(f);
	}

	/**
	 * 
	 */
	public void newStructure() {
		getBean().newStructure();
	}

	/**
	 * Saves the structure to a file.
	 * 
	 * @param f
	 *            The file to save the structure to.
	 * @throws FileFormatError
	 *             If there is an error with the extension
	 */
	public void saveStructureFromExtension(File f) throws FileFormatError {
		getBean().saveStructureFromExtension(f);
	}

	/**
	 * Set in the if the transitive closure View should be visible or not.
	 * 
	 * @param enableTransitiveClosureView
	 *            enables the transitive closure view.
	 */
	public void setShowTransitiveClosureView(boolean enableTransitiveClosureView) {
		boolean oldValue = showTransitiveClosureView;
		showTransitiveClosureView = enableTransitiveClosureView;
		this.firePropertyChange(PROPERTY_SHOW_TRANSITIVE_CLOSURE_VIEW,
				oldValue, showTransitiveClosureView);
	}

	private void initEventHandling() {
		// for debugging purposes
		this.addBeanPropertyChangeListener(new BeanChannelChangeListener(log));

		// notification of the selectionInList model if the underlying
		// domain model has changed.
		competenceManager.addPropertyChangeListener(
				CompetenceManager.PROPERTY_CbKST_VERTICES,
				new PropertyChangeListener() {
					@Override
					public void propertyChange(PropertyChangeEvent evt) {
						Object newValue = evt.getNewValue();
						@SuppressWarnings("unchecked") Set<CbKSTCompetence> value = (Set<CbKSTCompetence>) newValue;
						// competenceSelection.setList(value);
						ReadWriteLock s = competencesEventList
								.getReadWriteLock();
						s.writeLock().lock();
						competenceSelection.clearSelection();
						competencesEventList.clear();
						competencesEventList.addAll(value);
						s.writeLock().unlock();
					}
				});
		competenceManager.addPropertyChangeListener(
				CompetenceManager.PROPERTY_PREREQUESITE_GRAPH,
				new PropertyChangeListener() {

					@SuppressWarnings("unchecked")
					@Override
					public void propertyChange(PropertyChangeEvent evt) {
						PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> newValue = (PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>) evt
								.getNewValue();
						competencesTreeListFormat.setGraph(newValue);
					}
				});

	}

	/**
	 * Initialisation and Synchronisation opf submodels.
	 */
	private void initModels() {
		final CompetenceManager prerequesiteVisDomainModel = getBean();

		// 1. Synchronize a selected Competence Object in a Swing List with a
		// corresponding Competence Model (Jgoodies - Access to domain
		// entities)
		// 2. Reflect any changes made in the Competence Model in terms
		// of a changed name of the competence to the list view of the
		// GlazedLists. So to speak to inform the list that there was a change
		// to the mutable Competence Object it contains.

		// Create a glazed list that observes changes in mutable list elements.
		// The GlazedLists.beanConnector call adds an observer to all bean
		// properties of the CbKSTCompetence.
		EventList<CbKSTCompetence> eventList = GlazedLists
				.eventList(competenceManager.getCbKSTVertices());
		// filtered and observable list event model.
		competencesEventList = new FilterList<CbKSTCompetence>(
				new ObservableElementList<CbKSTCompetence>(eventList,
						GlazedLists.beanConnector(CbKSTCompetence.class)));

		competenceListModel = new EventListModel<CbKSTCompetence>(
				competencesEventList);

		competenceTableModel = new EventTableModel<CbKSTCompetence>(
				competencesEventList, new TableFormat<CbKSTCompetence>() {

					@Override
					public int getColumnCount() {
						return 3;
					}

					@Override
					public String getColumnName(int column) {
						switch (column) {
						case 0:
							return "ID";
						case 1:
							return "Name";
						case 2:
							return "Description";
						default:
							return "Unknown";
						}
					}

					@Override
					public Object getColumnValue(CbKSTCompetence baseObject,
							int column) {
						switch (column) {
						case 0:
							return baseObject.getId();
						case 1:
							return baseObject.getName();
						case 2:
							return baseObject.getDescription();
						default:
							return "Column Out of Reach";
						}
					}
				});
		competencesTreeListFormat = new PrerequesiteTreeListFormat(
				prerequesiteVisDomainModel.getStructure());

		competencesTreeModel = new EventTreeModel<CbKSTCompetence>(
				new TreeList<CbKSTCompetence>(competencesEventList,
						competencesTreeListFormat,
						new DefaultExpansionModel<CbKSTCompetence>(true)));

		// Using Jgoodies SelectionInList to bind the list selection to the
		// Presentation Model
		// of the Competence. Jgoodies (1) adapts the selected element of the
		// ListModel to the bean of the presentation model. Any updated in the
		// selection of the list thereby also updates the competence editing
		// view.
		competenceSelection = new SelectionInList<CbKSTCompetence>(
				competenceListModel, new BeanAdapter<CompetenceModel>(
						competenceModel, true).getValueModel("bean"));

	}

	public FilterList<CbKSTCompetence> getCompetencesEventList() {
		return competencesEventList;
	}

	/**
	 * For debugging purposes
	 * 
	 * @author georgo
	 * 
	 */
	private static class BeanChannelChangeListener implements
			PropertyChangeListener {

		private final Logger	log;

		public BeanChannelChangeListener(Logger log) {
			this.log = log;
		}

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			log.log(Level.FINE, "Old Value = {0} , New Value = {1}",
					new Object[] { evt.getOldValue(), evt.getNewValue() });
		}
	}

	/**
	 * The default state provider, that starts all nodes off as expanded.
	 */
	private static class DefaultExpansionModel<E> implements ExpansionModel<E> {
		private final boolean	expanded;

		public DefaultExpansionModel(boolean expanded) {
			this.expanded = expanded;
		}

		@Override
		public boolean isExpanded(E element, List<E> path) {
			return expanded;
		}

		@Override
		public void setExpanded(E element, List<E> path, boolean expanded) {
			// do nothing
		}
	}

	private class ApplyAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long	serialVersionUID	= 1L;

		public ApplyAction() {
			super("Apply");
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			triggerCommit();
		}
	}

}
